其實是因為計算簡單XD
如果知道RGB,可以透過這樣的公式快速轉成灰階值:
Gray = (R*38 + G*75 + B*15) >> 7
(來源:彩色轉灰階原理(RGB To Grey))
這樣的計算,在WebAssembly要手寫應該不難才對XD(實際上在小地方就是會出錯)
Canvas 2D Context提供了一個getImageData()
方法,可以從Canvas取得某範圍影像的RGBA資料。回傳的ImageData,其data屬性是一個陣列,內容分別是每個像素的R, G, B, A(Alpha),然後是下一個像素R, G, B, A等,依此類推。所以可以透過WebAssembly.Memory物件把這些資料傳入WebAssembly程式。
如果是用Javascript處理,其實很簡單就是了:
function jsgrayscale() {
let in_image_data = canvas.getImageData(0, 0, 640, 480);
for(let i=0; i<in_image_data.data.length; i+=4) {
let r = in_image_data.data[i];
let g = in_image_data.data[i+1];
let b = in_image_data.data[i+2];
let gr = (r*38 + g*75 + b*15) >> 7;
in_image_data.data[i] = gr;
in_image_data.data[i+1] = gr;
in_image_data.data[i+2] = gr;
}
canvas.putImageData(in_image_data, 0, 0);
}
用WebAssembly處理的話...為了怕寫回的時候出錯,改成input跟output資料放在不同位置的作法。傳給WebAssembly中的函數一個參數,也就是ImageData.data的長度,就會接著傳入的資料後面把東西寫入。
(module
(memory (import "js" "buf") 1)
(func (export "grey") (param $base i32)
(local $in_ptr i32)
(local $out_ptr i32)
(local $grey i32)
(local $r i32)
(local $g i32)
(local $b i32)
i32.const 0
set_local $in_ptr
get_local $base
set_local $out_ptr
(block $break
(loop $while
;; read R from memory and calculate
get_local $in_ptr
i32.load8_u
i32.const 38
i32.mul
set_local $r
i32.const 1
get_local $in_ptr
i32.add
tee_local $in_ptr
;; read G from memory and calculate
i32.load8_u
i32.const 75
i32.mul
set_local $g
i32.const 1
get_local $in_ptr
i32.add
tee_local $in_ptr
;; read B from memory and calculate
i32.load8_u
i32.const 15
i32.mul
set_local $b
i32.const 1
get_local $in_ptr
i32.add
set_local $in_ptr
;; calculate grey
get_local $r
get_local $g
i32.add
get_local $b
i32.add
i32.const 7
i32.shr_u
set_local $grey
;; write R to memory
get_local $out_ptr
get_local $grey
i32.store8
i32.const 1
get_local $out_ptr
i32.add
tee_local $out_ptr
;; write G to memory
get_local $grey
i32.store8
i32.const 1
get_local $out_ptr
i32.add
tee_local $out_ptr
;; write B to memory
get_local $out_ptr
get_local $grey
i32.store8
i32.const 1
get_local $out_ptr
i32.add
tee_local $out_ptr
;; copy opacity from and to memory
get_local $in_ptr
i32.load8_u
i32.store8
i32.const 1
get_local $out_ptr
i32.add
set_local $out_ptr
i32.const 1
get_local $in_ptr
i32.add
tee_local $in_ptr
;; if $in_ptr greater or equal to $base then break
get_local $base
i32.ge_u
br_if $break
;; next iteration
br $while
)
)
)
)
html端很簡單地把影像寫入Canvas,然後按個Grayscale按鈕,就可以把影像轉成灰階。
<html>
<body>
<canvas id="canvas" width="640" height="480"></canvas><br>
<button id="grayscale">Grayscale</button><br>
<div id="panel" style="display:none"></div>
<script src="../wasm_util.js"></script>
<script>
let canvas = document.getElementById('canvas').getContext('2d');
let img = new Image();
let size = 640 * 480 * 4;
let page_required = Math.floor(size * 2 / (64 * 1024)) + 1;
let instance;
let buf = new WebAssembly.Memory({initial:page_required});
let importObjects = {
js: {
buf: buf
}
};
img.src = 'IMG_1719_s.jpg';
img.onload = function() {
canvas.drawImage(img, 0, 0);
instance = new Wasm('test014.wasm').getInstance(importObjects);
console.log('image loaded');
}
document.getElementById('panel').appendChild(img);
document.getElementById('grayscale').onclick = wasmgrayscale;
function jsgrayscale() {
let in_image_data = canvas.getImageData(0, 0, 640, 480);
for(let i=0; i<in_image_data.data.length; i+=4) {
let r = in_image_data.data[i];
let g = in_image_data.data[i+1];
let b = in_image_data.data[i+2];
let gr = (r*38 + g*75 + b*15) >> 7;
in_image_data.data[i] = gr;
in_image_data.data[i+1] = gr;
in_image_data.data[i+2] = gr;
}
canvas.putImageData(in_image_data, 0, 0);
}
function wasmgrayscale() {
let in_image_data = canvas.getImageData(0, 0, 640, 480);
let view = new Uint8ClampedArray(buf.buffer);
for(let i=0; i<in_image_data.data.length; i++) {
view[i] = in_image_data.data[i];
}
instance
.then(instance => {
instance.exports.grey(in_image_data.data.length);
let view = new Uint8ClampedArray(buf.buffer);
for(let i=size; i< (size*2); i++) {
in_image_data.data[i-size] = view[i];
}
canvas.putImageData(in_image_data, 0, 0);
console.log('grayscale done');
});
}
</script>
</body>
</html>
(為了對照,裡面還有一個Javascript版的轉灰階函數jsgrayscale,這裡用的是WebAssembly裡的版本:wasmgrayscale)
先載入網頁,這時顯示的是彩色影像:
按下Grayscale按鈕後,就轉成灰階:
完工...
不過呢,其實程式寫了很多次XD...因為實際上在計算$in_ptr跟$out_ptr的時候有錯誤,$out_ptr多加了一次1,然後就出現Out of bound的錯誤,這是指定的記憶體位置超過超過記憶體範圍。還好重新檢查過後,發現錯誤,不然光靠這個資訊,也很難判斷問題出在哪裡。錯誤資訊雖然有標注出錯的行數,但這是反組譯出來的原始碼行數,並不是自己寫的程式的行數。
據說Firefox的開發工具,從54+版之後已經有除錯WebAssembly的功能,明天來試試看...(過年少寫一天程式)